package com.xu.security;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import com.xu.security.filter.AuthenticationTokenFilter;
import com.xu.security.handler.AccessDeniedHandlerImpl;
import com.xu.security.handler.AuthenticationEntryPointImpl;
import com.xu.security.handler.LogoutSuccessHandlerImpl;
import com.xu.security.prop.SecurityProperties;
import com.xu.security.token.TokenService;
import jakarta.annotation.Resource;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.CollectionUtils;

import java.time.Duration;

/**
 * security自动装配
 * @EnableConfigurationProperties({ SecurityProperties.class }) 引入配置类
 * @EnableWebSecurity 开始security认证
 * @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) 开启方法权限注解
 * @Author: 许乾伟
 * @Date: 2024/2/27 11:04
 */
@EnableConfigurationProperties({ SecurityProperties.class })
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class EnableAutoConfiguration {
    @Resource
    SecurityProperties properties;
    @Bean
    @Order(1)
    SecurityFilterChain filterChain(HttpSecurity http,
                                    LogoutSuccessHandler logoutSuccessHandler,
                                    AccessDeniedHandler accessDeniedHandler,
                                    AuthenticationEntryPoint authenticationEntryPoint,
                                    AuthenticationTokenFilter authenticationTokenFilter) throws Exception {
        //配置禁止csrf跨站请求保护
        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(request->{
                    //配置忽略验证请求
                    if (!CollectionUtils.isEmpty(properties.getIgnoreUri())){
                        properties.getIgnoreUri().forEach(url->{
                            request.requestMatchers(url).permitAll();
                        });
                    }
                    //开启所有请求授权
                    request .anyRequest().authenticated();
                })
                .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
                //配置异常处理
                .exceptionHandling(ex -> ex.accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint))
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 前后端分离本身就是无状态的，所以我们不需要cookie和session这类东西。所有的信息都保存在一个token之中。
                .sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        return http.build();
    }


    @Bean
    LogoutSuccessHandler logoutSuccessHandler(){
        return new LogoutSuccessHandlerImpl();
    }
    @Bean
    AccessDeniedHandler accessDeniedHandler(){
        return new AccessDeniedHandlerImpl();
    }

    @Bean
    AuthenticationEntryPoint authenticationEntryPoint(){
        return new AuthenticationEntryPointImpl();
    }
    @Bean
    AuthenticationTokenFilter authenticationTokenFilter(){
        return new AuthenticationTokenFilter();
    }

    /**
     * 添加密码策略bean
     * @return
     */
    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean("securityRedisTemplate")
    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public RedisTemplate<Object, Object> redisTemplate(@Qualifier("securityRedisFactory") LettuceConnectionFactory connectionFactory) {
        // 创建RedisTemplate对象
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(connectionFactory);
        // 创建JSON序列化器（两种设置Value和HashValue的JSON的序列化器）

        GenericFastJsonRedisSerializer fastJsonRedisSerializer
                = new GenericFastJsonRedisSerializer();
        // 设置Key和HashKey采用String序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置Value和HashValue采用JSON的序列化
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        return template;
    }
    @Bean("securityRedisFactory")
    public LettuceConnectionFactory lettuceConnectionFactory(){
        RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration();
        standaloneConfiguration.setHostName(properties.getRedis().getHost());
        standaloneConfiguration.setPort(properties.getRedis().getPort());
        standaloneConfiguration.setDatabase(properties.getRedis().getDatabase());
        standaloneConfiguration.setUsername(properties.getRedis().getUsername());
        standaloneConfiguration.setPassword(properties.getRedis().getPassword());
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(0);
        genericObjectPoolConfig.setMinIdle(8);
        genericObjectPoolConfig.setMaxTotal(8);
        genericObjectPoolConfig.setMaxWaitMillis(-1L);
        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(5000))
                .poolConfig(genericObjectPoolConfig)
                .build();
        LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(standaloneConfiguration,clientConfig);
        return connectionFactory;
    }

    @Bean
    TokenService tokenService(){
        return new TokenService();
    }
}
